\NeedsTeXFormat{LaTeX2e}

\ProvidesPackage{dprocess}[2018/01/02 Small package for data processing in LaTeX]

\RequirePackage{currfile-abspath}
\RequirePackage{etoolbox}
\RequirePackage{xstring}
\RequirePackage{pgfplotstable}
\RequirePackage{booktabs}
\RequirePackage{pgfkeys}
\RequirePackage{longtable}
\RequirePackage{shellesc}
\RequirePackage{multirow}
\RequirePackage{pgf}

\usepgflibrary{fpu}

%%%%%%%%%%%%%%%%%%%%%%%%% KEY SETUP %%%%%%%%%%%%%%%%%%%%%%%%%
\pgfkeys{%
	/dprocess/.is family,
}

%%%% Key rerouting to pgfplotstable %%%
\pgfkeys{/handlers/.unknown/.code = {%
	\fullexpandarg
	\IfBeginWith{\pgfkeyscurrentpath}{/dprocess/tables}{%
		\StrBehind{\pgfkeyscurrentpath}{/dprocess/tables}[\relPath]%
		%
		% take the relative key and try to pass it to pgfplots/table
		\def\newkey{/pgfplots/table\relPath/\pgfkeyscurrentname=\noexpand{#1}}%
		%
		% get the current value of /handler config
		\edef\prevValue{\ifdef{\pgfkeys@was@choice}{\pgfkeys@was@choice}{all}}
		
		% check if this is a valid value
		\IfStrEq{\prevValue}{all}{}{
			\IfStrEq{\prevValue}{only existing}{}{
				\IfStrEq{\prevValue}{full or existing}{}{
					\global\def\prevValue{all}
				}
			}
		}
		
		% allow macro definitions for rerouted keys
		\pgfkeys{/handler config=all}
		
		\edef\temp{\noexpand\pgfkeys{\newkey}}%
		\temp%
		
		% reset /handler config
		\edef\temp{\noexpand\pgfkeys{/handler config=\prevValue}}
		\temp
	}{%
		% emulate the error from pgfkeys
		\PackageError{pgfkeys}{I do not know the key '\pgfkeyscurrentpath/\pgfkeyscurrentname' , to which you passed '#1', and I am going to ignore it. Perhaps you misspelled it.}%
	}%
  },%
}

%%%% tables %%%%
\newif{\ifUnderlineHeader}
\newif{\ifUnderlineTable}
\newif{\ifOverlineHeader}

\def\@tables@userArgs{}

\pgfkeys{%
%	/handlers/first char syntax=true,
%	/handlers/first char syntax/begin-group character {/.initial = {},
	/dprocess,
	tables/.cd,
	table arguments/.store in = \@tables@userArgs,
	intelligent table type/.style = {
		/pgfplots/table/assign cell content/.code={%
			\@stringify{##1}{\@stringRepresentation}
			\@isPGFNumber{\@stringRepresentation}{%
				% format as number
				\pgfmathprintnumberto{\@stringRepresentation}\result
				\pgfkeyslet{/pgfplots/table/@cell content}\result
			}{%
				% it's not a number -> let default string-assignment take place
			}%
		},%
	},
	use longtable/.code = {%
		\pgfkeys{
			/pgfplots/table/.cd,
			begin table = \begin{longtable},%
			end table = \end{longtable},%
		}%
		\xdef\@tables@afterFirstRow{\expandonce{\@tables@afterFirstRow} \noexpand\endhead}
	},
	underline header/.is if = UnderlineHeader,
	overline header/.is if = OverlineHeader,
	underline table/.is if = UnderlineTable,
	substring replace/.style 2 args = {/pgfplots/table/string replace*={#1}{#2}},
	change dot to comma/.style = {substring replace = {.}{,}},
	comma as period/.style = {/pgf/number format/read comma as period},
	period/.style = {/pgf/number format/dec sep=#1},
	process table settings/.code={
		\ifUnderlineHeader
			\xdef\@tables@afterFirstRow{\noexpand\midrule \expandonce{\@tables@afterFirstRow}}
		\fi
		\ifUnderlineTable
			\xdef\@tables@afterLastRow{\noexpand\bottomrule \expandonce{\@tables@afterLastRow}}
		\fi
		\ifOverlineHeader
			\xdef\@tables@beforeFirstRow{\noexpand\toprule \expandonce{\@tables@beforeFirstRow}}
		\fi
	},
	transposed/.style = {/dprocess/tables/underline header = false},
	display rows/.style 2 args = {%
		/pgfplots/table/row predicate/.code={%
				\ifnum##1>0%
					% don't throw away the header
					\ifnum##1<#1\relax%
						\pgfplotstableuserowfalse%
					\fi%
					%
					\ifnum##1>#2\relax%
						\pgfplotstableuserowfalse%
					\fi%
				\fi%
			}%
	},
}

%%%% tables default %%%%
\pgfkeys{%
	/pgf/number format/use comma,%
	/pgf/number format/1000 sep = {\,},%
	/dprocess,%
	tables/defaults/.style = {%
		overline header,%
		underline table,%
		underline header,%
		col sep = tab,%
	}%
}

%%%%%%%%%%%%%%%%%%%%%%%%% CHECKING %%%%%%%%%%%%%%%%%%%%%%%%%
% make sure shell-escape is activated
\IfFileExists{shellEscapeTest.txt}{}{%
	\immediate\write18{echo 'shell escape activated' >> shellEscapeTest.txt}%
	
	\IfFileExists{shellEscapeTest.txt}{}{%
		\PackageError{dprocess}{Shell-escape is disabled}{In order for this package to work you have to run latex with the --shell-escape option}%
	}
}


%%%%%%%%%%%%%%%%%%%%%%%%% INTERNAL MACRO DEFS %%%%%%%%%%%%%%%%%%%%%%%%%
% the internal macro holding the set spreadsheet
\def\@spreadSheet{}

%the internal path to the jar
\def\@utilityBoxPath{}

% check if executable-path is valid
\def\@validateExecutable{%
	\ifdefempty{\@utilityBoxPath}{%
		\PackageError{dprocess}{No executable given}{Use \protect\executable{} to set the path to the UtilityBox.jar}%
	}{}%
}

% the directory all extarcted sub-tables should be stored in
\def\@subtableDir{subtables}
% the name of the log file for the table-extraction
\getabspath{\jobname.tex}
\edef\@tableExtractionLog{\theabsdir tableExtraction.log}

% function to re-empty all set table-values
\def\@tables@resetValues{%
	\global\def\@tables@afterFirstRow{}%
	\global\def\@tables@beforeFirstRow{}%
	\global\def\@tables@afterLastRow{}%
	\global\def\@tables@userArgs{}%
}

% call in order to make all these values actually defined
\@tables@resetValues

% function to override all provided macros with empty definitions
\def\@emptyDeclaredCommands{%
	\renewcommand{\executable}[1]{\relax}%
	\renewcommand{\spreadsheet}[1]{\relax}%
	\renewcommand{\thespreadsheet}{\relax}%
	\renewcommand{\extractsubtables}{\relax}%
	\renewcommand{\insertTable}[2][]{\relax}%
	%
	%\ShellEscape{echo 'cleared macros' >> jobname.txt}%
}

% internal macro for getting an unexpanded string representation of the given input
\def\@stringify#1#2{%
	\edef#2{\detokenize{#1}}
}

% internal macro checking whether the given input is a valid number for PGF
\newcommand{\@isPGFNumber}[3]{% \@isPGFNumber{<Number>}{<True>}{<False>}
	\begingroup
	\pgfkeys{/pgf/fpu/handlers/invalid number/.code={}}%
	\pgfmathfloatparsenumber{#1}%
	\global\pgfmathfloatgetflagstomacro\pgfmathresult\@pgfFloatParseFlag%
	\endgroup
	\ifnum\@pgfFloatParseFlag=3%
		#3%
	\else%
		#2%
	\fi%
}


%%%%%%%%%%%%%%%%%%%%%%%%% PROVIDED COMMANDS %%%%%%%%%%%%%%%%%%%%%%%%%
% command for setting the path to the executable jar
\DeclareRobustCommand{\executable}[1]{%
	\IfFileExists{#1}{
		% it does exist
		\global\def\@utilityBoxPath{#1}
	}{
		% it doesn't exist
		\PackageError{dprocess}{The given executable does not exist (#1)}{The given executable does not exist (#1)}
	}
}


% command for setting the spreadsheet to work on
\DeclareRobustCommand{\spreadsheet}[1]{%
	\IfFileExists{#1}{
		% it exists
		% test for ods file extension
		%\expandafter\MakeLowercase
		\StrBehind{#1}{.}[\@fileExtension]
		
		\IfStrEq{\@fileExtension}{ods}{%
			% it is indeed a .ods spreadsheet
			\global\def\@spreadSheet{#1}%
		}{%
			% it is a different file format
			\PackageError{dprocess}{Only .ods spreadsheets are supported (got .\@fileExtension)}{Only .ods spreadsheets are supported (got .\@fileExtension)}%
		}%
	}{
		% it doesn't exist
		\PackageError{dprocess}{The given spreadsheet (#1) does not exist}{The given spreadsheet (#1) does not exist!}%
	}%
}

% command for accessing the set spreadsheet
\DeclareRobustCommand{\thespreadsheet}{\@spreadSheet}


% command for extracting data from the ODS spreadsheet to CSV sub-tables
\DeclareRobustCommand{\extractsubtables}{
	\@validateExecutable
	
	% the internal macro containing the spreadSheet's absolute path
	\getabspath{\@spreadSheet}
	\let\@spreadSheetPath\theabspath
	
	\ifdefempty\@spreadSheetPath{%
		\PackageError{dprocess}{Can't locate absolute path of the spreadheet. Perhaps you need to compile a second time.}{The \protect\getabspath{} macro (currfile) wasn't able to locate the spreadsheet. This might be because no .fls file was created. In this case try the --recorder option.}
	}{
		\ShellEscape{java -jar -Dfile.encoding=UTF-8 "\@utilityBoxPath" -logFile="\@tableExtractionLog" -ExtractODSToCSVAction spreadSheet="\@spreadSheetPath" targetDir="\@subtableDir" replaceEmpty="{}" addTransposed=true} 
	}
}


% typeset a table from a CSV
\DeclareRobustCommand{\insertTable}[2][]{%	
	\begingroup
		% make sure all keys get rerouted properly
		\pgfkeys{/handler config=only existing}
		
		% process keys
		\pgfkeys{/dprocess/tables/.cd, defaults,#1}%
		
		% back to normal behaviour
		\pgfkeys{/handler config=all}
		%
		% check if accessed file exists
		\edef\@csvPath {#2}%
		%
		\IfFileExists{\@csvPath}{}{%
			\let\@copy\@csvPath%
			%
			\xdef\@csvPath {./\@subtableDir/\@copy}%
			%
			\IfFileExists{\@csvPath}{%
			%
			}{%
				\PackageError{dprocess}{The specified data file '\@csvPath' does not exist}{}%
			}%
		}%
		%
		% define sub-macro to avoid expansion problems
	    \xdef\doPrintTable{%
	    	\noexpand\pgfplotstabletypeset[%
	    		/dprocess/tables/process table settings,%
	    		every first row/.append style={before row=\noexpand\@tables@beforeFirstRow\noexpand\relax,%
				after row=\noexpand\@tables@afterFirstRow\noexpand\relax},%
	     		every last row/.append style={after row=\noexpand\@tables@afterLastRow\noexpand\relax},%
	     		header = false,%
	    		/dprocess/tables/intelligent table type,%
	     		every head row/.style={output empty row},%
	     		\expandonce\@tables@userArgs%,
	     	]%
	     }%
	     %
	     % typeset the actual table
	     \doPrintTable{\@csvPath}%
     \endgroup
     
     \@tables@resetValues%
}




%%%%%%%%%%%%%%%%%%%%%%%%% POST-CHECKING %%%%%%%%%%%%%%%%%%%%%%%%%
\IfFileExists{\jobname.tex}{
	% It is the main document (or another user-created sub-document) that is being compiled
	%\ShellEscape{echo 'main job: \jobname' >> jobname.txt}
}{
	% Somehting else is going on -> e.g. a run triggered by tikzexternalize
	%\ShellEscape{echo 'side job: \jobname' >> jobname.txt}%
	%
	% Don't do anything in such an environment
	\@emptyDeclaredCommands%
}